/* DefaultProcessor.java
 *
 * Copyright 2006, Tim Dwelle.
 *
 * Licensed under the Apache License, Version 2.0 (the
 * "License"); you may not use this file except in
 * compliance with the License.  You may obtain a copy of
 * the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in
 * writing, software distributed under the License is
 * distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR
 * CONDITIONS OF ANY KIND, either express or implied.  See
 * the License for the specific language governing
 * permissions and limitations under the License.
 *
 */

package dowry.impl;

import java.io.*;
import java.util.*;
import javax.servlet.*;
import javax.servlet.http.*;
import uk.ltd.getahead.dwr.*;
import uk.ltd.getahead.dwr.impl.HtmlConstants;
import uk.ltd.getahead.dwr.util.*;
import dowry.*;
import dowry.dwr.*;

/**
 * DefaultProcessor provides a DWR processror that (in
 * addition to all its normal DWR-ish responsibilities)
 * handles requests for the following scripts:
 *
 * <pre>
 * dowry/all.js        script containing all of Dowry's
 *                     contents in a single script.  This
 *                     combines the contents of the
 *                     following discrete scripts:
 *
 *                     dowry/core.js
 *                     dowry/cfg.js
 *
 * dowry/core.js       script containing Dowry's core
 *                     contents.  This combines the contents
 *                     of the following discrete scripts:
 *
 *                     dowry/loadJs/dowry.loader.ClassLoader.js
 *                     dowry/loadJs/$.js
 *                     dowry/loadJs/dowry.util.CssUtils.js
 *                     dowry/loadJs/dowry.util.EventUtils.js
 *                     dowry/loadJs/dowry.util.MessageUtils.js
 *                     dowry/loadJs/Dowry.js
 *
 * dowry/cfg.js        script that configures the JS
 *                     datatypes, entities, and remote based
 *                     on the setup of the entity mapper.
 *                     This combines the contents of the
 *                     following discrete scripts:
 *
 *                     engine.js
 *                     dowry/datatypes.js
 *                     dowry/entities.js
 *                     dowry/init.js
 *                     dowry/messages.js
 *                     dowry/remote.js
 *
 *
 * dowry/datatypes.js  script that configures the JS
 *                     datatypes only
 *
 * dowry/entities.js   script that configures the JS
 *                     entities only
 *
 * dowry/messages.js   script that contains the JS
 *                     message bundle only
 *
 * dowry/init.js       script that configures the Dowry
 *                     intialization on page load
 *
 * dowry/remote.js     script that configures the Dowry
 *                     remote services
 * </pre>
 *
 */
public class DefaultProcessor extends uk.ltd.getahead.dwr.impl.DefaultProcessor
{
    public static String FILE_DOWRY_ALL       = "/dowry/all.js";
    public static String FILE_DOWRY_CFG       = "/dowry/cfg.js";
    public static String FILE_DOWRY_CORE      = "/dowry/core.js";
    public static String FILE_DOWRY_DATATYPES = "/dowry/datatypes.js";
    public static String FILE_DOWRY_ENTITIES  = "/dowry/entities.js";
    public static String FILE_DOWRY_INIT      = "/dowry/init.js";
    public static String FILE_DOWRY_MESSAGES  = "/dowry/messages.js";
    public static String FILE_DOWRY_REMOTE    = "/dowry/remote.js";
    public static String PATH_LOAD_JS         = "/dowry/loadJs/";

    private AccessControl     accessControl;
    private ConverterManager  converterMgr;
    private CreatorManager    creatorMgr;
    private DatatypeManager   datatypeMgr;
    private DowryService      dowryService;
    private EntityMapper      entityMapper;
    private Javascript        javascript;
    private boolean           registered = false;

    /**
     * Returns the access control used by this processor.
     *
     * @return  the access control
     *
     */
    public AccessControl getAccessControl()
    {
        return accessControl;
    }

    /**
     * Returns the converter manager used by this processor.
     *
     * @return  the converter manager
     *
     */
    public ConverterManager getConverterManager()
    {
        return converterMgr;
    }

    /**
     * Returns the creator manager used by this processor.
     *
     * @return  the creator manager
     *
     */
    public CreatorManager getCreatorManager()
    {
        return creatorMgr;
    }

    /**
     * Returns the datatype manager used by this processor.
     *
     * @return  the datatype manager
     *
     */
    public DatatypeManager getDatatypeManager()
    {
        return datatypeMgr;
    }

    /**
     * Returns the Dowry service used by this processor.
     *
     * @return  the Dowry service
     *
     */
    public DowryService getDowryService()
    {
        return dowryService;
    }

    /**
     * Returns the entity mapper used by this processor.
     *
     * @return  the entity mapper
     *
     */
    public EntityMapper getEntityMapper()
    {
        return entityMapper;
    }

    /**
     * Returns the javascript tool used by this processor.
     *
     * @return  the javascript tool
     *
     */
    public Javascript getJavascript()
    {
        return javascript;
    }

    /**
     * Handles requests to the processor.
     *
     * @param req  the servlet request
     *
     * @param res  the response response
     *
     */
    public void handle(HttpServletRequest req, HttpServletResponse res)
        throws ServletException, IOException
    {
        registerDowryInternals();

        boolean handled = false;
        String pathInfo = req.getPathInfo();

        if (pathInfo != null &&
            req != null &&
            res != null &&
            req instanceof DwrRequest &&
            res instanceof DwrResponse)
        {
            DwrRequest  reqWrapper = (DwrRequest) req;
            DwrResponse resWrapper = (DwrResponse) res;

            handled = true;

            if (pathInfo.equalsIgnoreCase(FILE_DOWRY_ALL))
            {
                doDowryEngineFile(reqWrapper, resWrapper);
                doDwrEngineFile(reqWrapper, resWrapper);
                doDowryRemoteFile(reqWrapper, resWrapper);
                doDowryEntitiesFile(reqWrapper, resWrapper);
                doDowryDatatypesFile(reqWrapper, resWrapper);
                doDowryMessagesFile(reqWrapper, resWrapper);
                doDowryInitFile(reqWrapper, resWrapper);
            }
            else if (pathInfo.equalsIgnoreCase(FILE_DOWRY_CORE))
            {
                doDowryEngineFile(reqWrapper, resWrapper);
            }
            else if (pathInfo.equalsIgnoreCase(FILE_DOWRY_CFG))
            {
                doDwrEngineFile(reqWrapper, resWrapper);
                doDowryRemoteFile(reqWrapper, resWrapper);
                doDowryEntitiesFile(reqWrapper, resWrapper);
                doDowryDatatypesFile(reqWrapper, resWrapper);
                doDowryMessagesFile(reqWrapper, resWrapper);
                doDowryInitFile(reqWrapper, resWrapper);
            }
            else if (pathInfo.equalsIgnoreCase(FILE_DOWRY_DATATYPES))
            {
                doDowryDatatypesFile(reqWrapper, resWrapper);
            }
            else if (pathInfo.equalsIgnoreCase(FILE_DOWRY_ENTITIES))
            {
                doDowryEntitiesFile(reqWrapper, resWrapper);
            }
            else if (pathInfo.equalsIgnoreCase(FILE_DOWRY_MESSAGES))
            {
                doDowryMessagesFile(reqWrapper, resWrapper);
            }
            else if (pathInfo.equalsIgnoreCase(FILE_DOWRY_INIT))
            {
                doDowryInitFile(reqWrapper, resWrapper);
            }
            else if (pathInfo.equalsIgnoreCase(FILE_DOWRY_REMOTE))
            {
                doDowryRemoteFile(reqWrapper, resWrapper);
            }
            else if (pathInfo.startsWith(PATH_LOAD_JS))
            {
                doDowryLoadJs(reqWrapper, resWrapper);
            }
            else
            {
                handled = false;
            }

            if (handled)
            {
                PrintWriter out = res.getWriter();
                out.flush();
                out.close();
            }
        }

        if (!handled)
        {
            super.handle(req, res);
        }
    }

    /**
     * Sets the access control used by this processor.
     *
     * @param accessControl  the access control
     *
     */
    public void setAccessControl(AccessControl accessControl)
    {
        this.accessControl = accessControl;
    }

    /**
     * Sets the converter manager used by this processor.
     *
     * @param converterMgr  the converter manager
     *
     */
    public void setConverterManager(ConverterManager converterMgr)
    {
        this.converterMgr = converterMgr;
    }

    /**
     * Sets the creator manager used by this processor.
     *
     * @param creatorMgr  the creator manager
     *
     */
    public void setCreatorManager(CreatorManager creatorMgr)
    {
        this.creatorMgr = creatorMgr;
    }

    /**
     * Sets the datatype manager used by this processor.
     *
     * @param datatypeMgr  the datatype manager
     *
     */
    public void setDatatypeManager(DatatypeManager datatypeMgr)
    {
        this.datatypeMgr = datatypeMgr;
    }

    /**
     * Sets the Dowry service used by this processor.
     *
     * @param dowryService  the Dowry service
     *
     */
    public void setDowryService(DowryService dowryService)
    {
        this.dowryService = dowryService;
    }

    /**
     * Sets the entity mapper used by this processor.
     *
     * @param entityMapper  the entity mapper
     *
     */
    public void setEntityMapper(EntityMapper entityMapper)
    {
        this.entityMapper = entityMapper;
    }

    /**
     * Sets the javascript tool used by this processor.
     *
     * @param javascript  the javascript tool
     *
     */
    public void setJavascript(Javascript javascript)
    {
        this.javascript = javascript;
    }

    /**
     * Writes a script that configures Dowry's datatypes
     * based on the setup of the datatype manager.
     *
     * @param req  the servlet request
     *
     * @param res  the response response
     *
     */
    protected void doDowryDatatypesFile(DwrRequest req, DwrResponse res)
        throws ServletException, IOException
    {
        PrintWriter out = res.getWriter();
        res.setContentType(HtmlConstants.MIME_JS);

        Javascript js = getJavascript();
        EntityMapper entityMapper = getEntityMapper();
        DatatypeManager datatypeMgr = getDatatypeManager();

        if (js != null && entityMapper != null && datatypeMgr != null)
        {
            Map dt = new HashMap();

            Set datatypes = entityMapper.getTypes();
            Iterator datatypeIter = datatypes.iterator();
            while (datatypeIter.hasNext())
            {
                String key = (String) datatypeIter.next();
                if (key != null)
                {
                    Datatype datatype = datatypeMgr.getDatatypeByType(key);
                    if (datatype != null)
                    {
                        String jsClass = datatype.getJsClass();
                        if (jsClass != null)
                        {
                            dt.put(key, jsClass);
                        }
                    }
                }
            }

            out.println(js.var("Dowry.datatypes", dt, false));
        }
    }

    /**
     * Writes a script containing the Dowry engine code.
     *
     * @param req  the servlet request
     *
     * @param res  the response response
     *
     */
    protected void doDowryEngineFile(DwrRequest req, DwrResponse res)
        throws ServletException, IOException
    {
        PrintWriter out = res.getWriter();
        res.setContentType(HtmlConstants.MIME_JS);

        DowryService service = getDowryService();
        out.println(service.loadJs("dowry.loader.ClassLoader"));
        out.println(service.loadJs("$"));
        out.println(service.loadJs("dowry.util.CssUtils"));
        out.println(service.loadJs("dowry.util.EventUtils"));
        out.println(service.loadJs("dowry.util.MessageUtils"));
        out.println(service.loadJs("Dowry"));
    }

    /**
     * Writes a script that configures Dowry's entities
     * based on the setup of the entity mapper.
     *
     * @param req  the servlet request
     *
     * @param res  the response response
     *
     */
    protected void doDowryEntitiesFile(DwrRequest req, DwrResponse res)
        throws ServletException, IOException
    {
        PrintWriter out = res.getWriter();
        res.setContentType(HtmlConstants.MIME_JS);

        Javascript js = getJavascript();
        EntityMapper entityMapper = getEntityMapper();
        if (js != null && entityMapper != null)
        {
            Map entities = entityMapper.getEntityMapping();
            if (entities != null)
            {
                out.println(js.var("Dowry.entities", entities, false));
            }
        }
    }

    /**
     * Writes a script to initialize Dowry on page load.
     *
     * @param req  the servlet request
     *
     * @param res  the response response
     *
     */
    protected void doDowryInitFile(DwrRequest req, DwrResponse res)
        throws ServletException, IOException
    {
        PrintWriter out = res.getWriter();
        res.setContentType(HtmlConstants.MIME_JS);

        // this uses the  the Dean Edwards "onload
        // problem solved" solution
        //
        // http://dean.edwards.name/weblog/2005/09/busted/

        // mozilla
        out.println("if (document.addEventListener) " +
                    "{ "                              +
                        "document.addEventListener("  +
                            "\"DOMContentLoaded\", "  +
                            "Dowry._initializer, "    +
                            "false);"                 +
                    "}");

        // internet explorer
        out.println("/*@cc_on @*/");
        out.println("/*@if (@_win32)");
        out.println("document.write(\"<script defer>Dowry._intializer<"+
                    "/script>\");");
        out.println("/*@end @*/");

        // others
        out.println("Dowry.event.addEvent(window, \"load\", " +
                    "Dowry._initializer);");
    }

    /**
     * Writes a script that can be returned via the
     * loadJs() facility.
     *
     * @param req  the servlet request
     *
     * @param res  the response response
     *
     */
    protected void doDowryLoadJs(DwrRequest req, DwrResponse res)
        throws ServletException, IOException
    {
        PrintWriter out = res.getWriter();
        res.setContentType(HtmlConstants.MIME_JS);

        DowryService service = getDowryService();

        // cleanup import, derived from request url
        String importName = req.getPathInfo();

        importName = LocalUtil.replace(importName,
                                       PATH_LOAD_JS,
                                       HtmlConstants.BLANK);

        importName = LocalUtil.replace(importName,
                                       HtmlConstants.EXTENSION_JS,
                                       HtmlConstants.BLANK);

        // include file
        out.println(service.loadJs(importName));
    }

    /**
     * Writes a script that configures Dowry's localized
     * messages.
     *
     * @param req  the servlet request
     *
     * @param res  the response response
     *
     */
    protected void doDowryMessagesFile(DwrRequest req, DwrResponse res)
        throws ServletException, IOException
    {
        PrintWriter out = res.getWriter();
        res.setContentType(HtmlConstants.MIME_JS);

        Javascript js = getJavascript();

        Map messages = new HashMap();

        Iterator keys = dowry.Messages.keys();
        while (keys.hasNext())
        {
            String key = (String) keys.next();
            String msg = dowry.Messages.get(key);
            messages.put(key, msg);
        }

        out.println(js.var("Dowry._bundle", messages, false));
        out.println("Dowry.msgs = new dowry.util.MessageUtils(Dowry._bundle);");
        out.println("Dowry._bundle=null;");
    }

    /**
     * Writes a script that configures Dowry's remote
     * service and remote classloader.
     *
     * @param req  the servlet request
     *
     * @param res  the response response
     *
     */
    protected void doDowryRemoteFile(DwrRequest req, DwrResponse res)
        throws ServletException, IOException
    {
        PrintWriter out = res.getWriter();
        res.setContentType(HtmlConstants.MIME_JS);

        DowryService service = getDowryService();

        // include script for the remote service
        RequestDispatcher rd = req.getRequestDispatcher("/dwr");
        req.setPathInfo("/interface/DowryService.js");
        rd.include(req, res);

        // include the remote classloader
        out.println(service.loadJs("dowry.loader.RemoteClassLoader"));

        // configure the remote classloader
        // as the default classloader
        out.println("$classLoader="+
                    "new dowry.loader.RemoteClassLoader("+
                    "$classLoader, DowryService);");

        // configure dowry to use the remote service
        out.println("Dowry.remote=DowryService;");
    }

    /**
     * Writes DWR's 'engine.js' script.
     *
     * @param req  the servlet request
     *
     * @param res  the response response
     *
     */
    protected void doDwrEngineFile(DwrRequest req, DwrResponse res)
        throws ServletException, IOException
    {
        res.setContentType(HtmlConstants.MIME_JS);
        RequestDispatcher rd = req.getRequestDispatcher("/dwr");
        req.setPathInfo("/engine.js");
        rd.include(req, res);
    }

    /**
     * Registers the DWR converters and services needed for
     * Dowry.
     *
     */
    protected synchronized void registerDowryInternals()
    {
        if (!registered)
        {
            registerEntities();
            registerService();
        }
    }

    /**
     * Registers the DWR converters for all classes
     * reported from the entity mapper.
     *
     */
    protected void registerEntities()
    {
        EntityMapper entityMapper = getEntityMapper();
        ConverterManager converterMgr = getConverterManager();

        if (entityMapper != null &&
            converterMgr != null &&
            creatorMgr != null)
        {
            Set classes = entityMapper.getClasses();
            if (classes != null)
            {
                Iterator classIter = classes.iterator();
                while (classIter.hasNext())
                {
                    Class c = (Class) classIter.next();
                    if (c != null)
                    {
                        try
                        {
                            converterMgr.addConverter(c.getName(),
                                                      "bean",
                                                      new HashMap());
                        }
                        catch(Exception ex)
                        {
                        }
                    }
                }
            }
        }
    }

    /**
     * Registers the DWR service for Dowry.
     *
     */
    protected void registerService()
    {
        CreatorManager creatorMgr = getCreatorManager();
        AccessControl accessControl = getAccessControl();
        DowryService service = getDowryService();
        if (creatorMgr != null && accessControl != null && service != null)
        {
            try
            {
                Map params = new HashMap();
                params.put("class", service.getClass().getName());
                params.put("scope", Creator.APPLICATION);

                creatorMgr.addCreator("new", "DowryService", params);

                accessControl.addIncludeRule("DowryService", "loadJs");
                accessControl.addIncludeRule("DowryService", "validate");
                accessControl.addIncludeRule("DowryService", "validateMany");
            }
            catch(Exception ex)
            {
            }

            registered = true;
        }
    }
}